import building
import faction
import game
import marshal
import player
import pyglet
import random
import select
import socket
import SocketServer
import struct
import sys
import threading
import time
import unit
import vector


class SocketCommunicator:
	'''Base socket communicator class.
	'''

	control_fmt = '!l'
	control_size = struct.calcsize(control_fmt)
	buffer_size = 4096

	def __init__(self, latency=0, packet_loss=0):
		self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
		self.socket.setblocking(False)
		self.received = []
		self.latency = latency
		self.packet_loss = packet_loss
		thread = threading.Thread(target=self._start)
		thread.setDaemon(True)
		thread.start()

	def send(self, address, *args):
		try:
			payload = marshal.dumps(args)
			data = struct.pack(self.control_fmt, len(payload)) + payload
			self.socket.sendto(data, address)
		except socket.error:
			pass

	def _start(self):
		try:
			while True:
				try:
					select.select([self.socket], [], [])
					data, address = self.socket.recvfrom(self.buffer_size)
					if random.random() < self.packet_loss:
						continue # artificial packet loss
					payload_size, = struct.unpack(self.control_fmt, data[:self.control_size])
					payload = marshal.loads(data[self.control_size:])
					if self.latency:
						# artificial latency
						until = time.time() + random.expovariate(1.0 / self.latency)
					else:
						until = None
					self.received.append((until, address, payload))
				except (socket.error, struct.error, EOFError, ValueError, TypeError):
					pass
		finally:
			self.socket.close()


class Server(SocketCommunicator):
	'''Generic network server.
	'''

	def __init__(self, host, port, latency=0, packet_loss=0):
		SocketCommunicator.__init__(self, latency, packet_loss)
		self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
		self.socket.bind((host, port))
		self.server_generation = 0
		self.client_generation = {}

	def tick(self):
		self.send_server_state()
		self.receive_client_messages()
		self.server_generation += 1

	def send_server_state(self):
		for address, client_generation in self.client_generation.iteritems():
			self.send(address, self.server_generation, client_generation, self.get_server_state())

	def receive_client_messages(self):
		delayed = []
		while self.received:
			next = self.received.pop(0)
			until, address, (server_generation, client_generation, message) = next
			if until < time.time():
				if client_generation == (self.client_generation.get(address, 0) + 1):
					self.process_client_message(message)
					self.client_generation[address] = client_generation
			else:
				delayed.append(next)
		self.received.extend(delayed)

	def process_client_message(self, message):
		raise NotImplementedError

	def get_server_state(self):
		raise NotImplementedError


class Client(SocketCommunicator):
	'''Generic network client.
	'''

	def __init__(self, host, port, latency=0, packet_loss=0):
		SocketCommunicator.__init__(self, latency, packet_loss)
		self.server = (host, port)
		self.server_generation = None
		self.client_generation = 0
		self.unacknowledged_messages = []

	def tick(self):
		self.send_client_messages()
		self.receive_server_state()

	def send_client_messages(self):
		for client_generation, message in self.unacknowledged_messages:
			self.send(self.server, self.server_generation, client_generation, message)
		for message in self.get_client_messages():
			self.client_generation += 1
			self.send(self.server, self.server_generation, self.client_generation, message)
			self.unacknowledged_messages.append((self.client_generation, message))

	def receive_server_state(self):
		delayed = []
		while self.received:
			next = self.received.pop()
			until, address, (server_generation, client_generation, server_state) = next
			while self.unacknowledged_messages and client_generation >= self.unacknowledged_messages[0][0]:
				del self.unacknowledged_messages[0]
			if until < time.time():
				if server_generation > self.server_generation:
					self.process_server_state(server_state)
					self.server_generation = server_generation
			else:
				delayed.append(next)
		self.received.extend(delayed)

	def process_server_state(self, server_state):
		raise NotImplementedError

	def get_client_messages(self):
		raise NotImplementedError


class NetworkMessager:

	CREATE_PLAYER = 1 # player joined game
	TALK = 2 # chat message
	CREATE_UNIT = 3 # create unit
	MOVE_UNIT = 4 # unit move
	CREATE_BUILDING = 5 # create building

	def __init__(self):
		self.messages = []

	def on_request_player_create(self, player_id, name, faction_id):
		self.messages.append((self.CREATE_PLAYER, (player_id, name, faction_id)))

	def on_request_talk(self, msg):
		self.messages.append((self.TALK, (msg,)))

	def on_request_unit_create(self, class_name, owner_id, health, location, destination):
		self.messages.append((self.CREATE_UNIT, (class_name, owner_id, health, location.x, location.y, location.z, destination.x, destination.y, destination.z)))

	def on_request_unit_move(self, unit_id, destination):
		self.messages.append((self.MOVE_UNIT, (unit_id, destination.x, destination.y, destination.z)))

	def on_request_building_create(self, class_name, owner_id, health, location):
		self.messages.append((self.CREATE_BUILDING, (class_name, owner_id, health, location.x, location.y, location.z)))


class MechazoidServer(Server, NetworkMessager):

	def __init__(self, *args):
		Server.__init__(self, *args)
		NetworkMessager.__init__(self)
		game.state.push_handlers(self)
		self.talk_msg = ''

	def get_server_state(self):
		server_state = {}

		server_state['talk'] = self.talk_msg

		server_state['players'] = {}
		for p in player.Player.group:
			server_state['players'][p.id] = (p.name, p.faction.id, p.mineral, p.gas)

		server_state['buildings'] = {}
		for b in building.Building.group:
			server_state['buildings'][b.id] = (b.__class__.__name__, b.owner.id, b.health, b.size, b.location.x, b.location.y, b.location.z)

		server_state['units'] = {}
		for u in unit.Unit.group:
			server_state['units'][u.id] = (u.__class__.__name__, u.owner.id, u.health, u.location.x, u.location.y, u.location.z, u.size, u.target and u.target.id or None, u.destination and u.destination.x, u.destination and u.destination.y, u.destination and u.destination.z)

		return server_state

	def process_client_message(self, message):
		message_type, args = message

		if message_type == self.MOVE_UNIT:
			unit_id, wx, wy, wz = args
			game.state.unit_move(unit_id, vector.Vec3(wx, wy, wz))

		elif message_type == self.CREATE_UNIT:
			class_name, owner_id, health, x, y, z, wx, wy, wz = args
			game.state.unit_create(None, class_name, player.Player.group.get(owner_id), health, vector.Vec3(x, y, z), None, vector.Vec3(wx, wy, wz))

		elif message_type == self.CREATE_BUILDING:
			class_name, owner_id, health, x, y, z = args
			game.state.building_create(None, class_name, player.Player.group.get(owner_id), health, vector.Vec3(x, y, z), None)

		elif message_type == self.TALK:
			msg, = args
			game.state.talk(msg)
			self.talk_msg = msg

		elif message_type == self.CREATE_PLAYER:
			player_id, name, faction_id = args
			if player_id not in player.Player.group:
				game.state.player_create(player_id, name, faction.get(faction_id), None, None)

	def on_update(self, dt):
		# kill anything that's dead
		for u in unit.Unit.group:
			if u.health <= 0:
				game.state.unit_kill(u.id)
		for b in building.Building.group:
			if b.health <= 0:
				game.state.building_kill(b.id)
		# TODO: kill players

		# networked messages
		Server.tick(self)

		# local messages
		while self.messages:
			self.process_client_message(self.messages.pop(0))


class MechazoidClient(Client, NetworkMessager):

	def __init__(self, *args):
		Client.__init__(self, *args)
		NetworkMessager.__init__(self)
		game.state.push_handlers(self)

	def get_client_messages(self):
		client_messages = []
		while self.messages:
			client_messages.append(self.messages.pop(0))
		return client_messages

	def process_server_state(self, server_state):

		# chat

		if server_state['talk']:
			game.state.talk(server_state['talk'])

		# create/update objects

		for player_id, p in server_state['players'].items():
			name, faction_id, mineral, gas = p
			p = player.Player.group.get(player_id)
			f = faction.get(faction_id)
			if p:
				game.state.player_state_update(player_id, name, f, mineral, gas)
			else:
				game.state.player_create(player_id, name, f, mineral, gas)

		for building_id, b in server_state['buildings'].items():
			class_name, owner_id, health, size, x, y, z = b
			owner = player.Player.group.get(owner_id)
			b = building.Building.group.get(building_id)
			location = vector.Vec3(x, y, z)
			if b:
				game.state.building_state_update(building_id, owner, health, location, size, None)
			else:
				game.state.building_create(building_id, class_name, owner, health, location, size)

		for unit_id, u in server_state['units'].items():
			class_name, owner_id, health, x, y, z, size, wx, wy, wz, target_id = u
			owner = player.Player.group.get(owner_id)
			u = unit.Unit.group.get(unit_id)
			location = vector.Vec3(x, y, z)
			destination = None
			if None not in (wx, wy, wz):
				destination = vector.Vec3(wx, wy, wz)
			target = unit.Unit.group.get(target_id) or building.Building.group.get(target_id)
			if u:
				game.state.unit_state_update(unit_id, owner, health, location, size, target, destination)
			else:
				game.state.unit_create(unit_id, class_name, owner, health, location, target, destination)

		# destroy objects

		for u in unit.Unit.group:
			if u.id not in server_state['units']:
				game.state.unit_kill(u.id)

		for b in building.Building.group:
			if b.id not in server_state['buildings']:
				game.state.building_kill(b.id)

		for p in player.Player.group:
			if p.id not in server_state['players']:
				game.state.player_kill(p.id)

	def on_update(self, dt):
		Client.tick(self)

